﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Controls;
using System.Windows.Controls.DataVisualization.Charting;
using System.Windows.Media;
using System.Collections.ObjectModel;
using System.Windows;
using snat.algorithms;
using snat.util;
using System.Threading;
using System.Windows.Threading;
using algorithms;
using System.Windows.Media.Animation;

namespace gui {
    /// <summary>
    /// Interaction logic for ChartTab.xaml (Data Charts Tab)
    /// </summary>
    public partial class ChartTab : UserControl {

        private DataChart chart;
        private int chartPointLimit = 500;
        public static AlgorithmItem SelectedAlgorithm { get; private set; }
        public static Dispatcher _Dispatcher { get; private set; }

        // Spinner events
        public static event pcRunning RunningEvent;
        public static event pcReady ReadyEvent;

        public ChartTab() {
            InitializeComponent();

            // Setup Title and Description Binding
            ChartInfo._Dispatcher = this.Dispatcher;
            this.DataContext = ChartInfo.Instance;

            // Populate the algorithms list
            ChartInfo.Instance.AlgorithmsList = new ObservableCollection<AlgorithmItem>();
            Initialise.AlgorithmsListBuiltEvent += PopulateFilterList;

            // Setup Combobox
            SeriesType.ItemsSource = new TypeStringTuple[]
            {
                new TypeStringTuple(typeof(LineSeries), "Line Chart", true),
                new TypeStringTuple(typeof(ScatterSeries), "Scatter Chart", true),
                new TypeStringTuple(typeof(ColumnSeries), "Column Chart", true),
                new TypeStringTuple(typeof(AreaSeries), "Area Chart", true),
                new TypeStringTuple(typeof(PieSeries), "Pie Chart", true),
                new TypeStringTuple(typeof(BarSeries), "Bar Chart", true),
                new TypeStringTuple(typeof(StackedLineSeries), "Stacked Line Chart", false),
                new TypeStringTuple(typeof(StackedColumnSeries), "Stacked Column Chart", false),
                new TypeStringTuple(typeof(StackedAreaSeries), "Stacked Area Chart", false),
                new TypeStringTuple(typeof(StackedBarSeries), "Stacked Bar Chart", false),
            };
            SeriesType.SelectedIndex = 0;
            SeriesType.SelectionChanged += new SelectionChangedEventHandler(SeriesType_SelectionChanged);

            // Register GUI enable/disable events
            NetworkModelConnect.OnConnectingEvent += DisableCheckBoxes;
            NetworkModelConnect.OnConnectedEvent += DisableCheckBoxes;
            NetworkModelConnect.TapeLoadingEvent += DisableCheckBoxes;
            NetworkModelConnect.TapeLoadedEvent += EnableCheckBoxes;
            NetworkModelConnect.OnFinishedProcessingEvent += EnableCheckBoxes;
            PlayControl.RunningEvent += DisableCheckBoxes;
            PlayControl.ReadyEvent += EnableCheckBoxes;
            PlayControl.FinishedEvent += EnableCheckBoxes;

            // Register Menu events
            Menu.OnSaveChartJPGEvent += SaveChartJPG;
            Menu.OnSaveChartCSVEvent += SaveChartCSV;
            Menu.OnResetEvent += ResetChartTab;

            // Build empty chart
            UpdateChart(null);
            ChartProgress.Visibility = Visibility.Collapsed;
        }

        // Have the dispatcher build the filter list
        public void PopulateFilterList(ObservableCollection<AlgorithmItem> list) {
            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                new Action(delegate() {
                BuildFilterList(list);
            }));
        }

        // Populate Algorithm List list with selected algorithms from AlgorithmBox
        public void BuildFilterList(ObservableCollection<AlgorithmItem> list) {

            // Remove algorithm chart listeners, and clear current list
            if (SelectedAlgorithm != null) (SelectedAlgorithm.Algorithm as IChartRenderable).RenderChart -= UpdateChartData;
            ChartInfo.Instance.AlgorithmsList.Clear();

            // If the presented list contains at least one algorithm, add it to the available chart list
            if (list != null && list.Count > 0) {
                foreach (AlgorithmItem a in list) {
                    if (AlgorithmSet.Instance[a.Name] is IChartRenderable) {
                        ChartInfo.Instance.AlgorithmsList.Add(new AlgorithmItem(a.Algorithm, a.Name));
                    }
                }

                // For each IChartRenderable algorithm
                if (ChartInfo.Instance.AlgorithmsList.Count > 0) {
                    
                    // Make the first algorithm selected
                    bool first = true;
                    foreach (AlgorithmItem a in ChartInfo.Instance.AlgorithmsList) {
                        if (first) {
                            a.Selected = true;
                            SelectedAlgorithm = a;
                            first = false;
                        } else a.Selected = false;
                    }

                    // Add algorithm chart listener
                    if (SelectedAlgorithm != null) {
                        (SelectedAlgorithm.Algorithm as IChartRenderable).RenderChart += UpdateChartData;
                    }
                }
            }
        }


        #region **************** DATACHART UPDATE LOGIC ****************

        private void UpdateChartData(IAlgorithm alg) {
            if (SelectedAlgorithm.Algorithm.Equals(alg) && (alg is IChartRenderable)) {

                // Get latest algorithm DataChart
                DataChart newChart = (alg as IChartRenderable).GetRenderableChart();
                List<DataSeries> seriesList = new List<DataSeries>();
                foreach (DataSeries s in newChart.SeriesList.Values) seriesList.Add(s);

                bool nonEmptySeries = false;
                foreach (DataSeries s in seriesList) {
                    // Check to see if we have a non empty series
                    if (s.Series.Count > 0) nonEmptySeries = true;

                    // Ensure each DataSeries provided is of a safe length to visualise 
                    if (s.Series.Count > chartPointLimit) {
                        DataSeries seriesToReplace = new DataSeries(s.SeriesName);
                        int step = s.Series.Count / chartPointLimit;
                        int i = 0;
                        while (i < s.Series.Count) {
                            seriesToReplace.Series.Add(s.Series[i]);
                            i += step;
                        }
                        newChart.RemoveSeries(s.SeriesName);
                        newChart.AddSeries(seriesToReplace);
                    }
                }

                // If we have something to display, render the chart
                if (nonEmptySeries) {
                    LoadChart(newChart);
                }
            }
        }

        // Performance render method designed to call a low priority thread to render the new chart
        private readonly object _objLock = new object();
        private void LoadChart(DataChart dataChart) {
            if (SelectedAlgorithm != null) {

                // Display loading spinner
                if (RunningEvent != null) RunningEvent();

                // Display the loading bar
                Random random = new Random();
                int rand = random.Next(40);
                lock (_objLock) {
                    this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                        new Action(delegate() {
                        ChartProgress.Value = 50 + rand;
                        ChartProgress.Visibility = Visibility.Visible;
                    }));
                }

                // Render chart
                lock (_objLock) {
                    DispatcherOperation operation =
                        this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background,
                        new Action(delegate() {
                            UpdateChart(dataChart);
                        }
                    ));
                    operation.Completed += DispatcherOperation_Completed;
                }
            }
        }
        // Hide loading bar once rendering has completed
        private void DispatcherOperation_Completed(object sender, EventArgs e) {
            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                new Action(delegate() {
                ChartProgress.Visibility = Visibility.Collapsed;
            }));
            // Set spinner to ready
            if (ReadyEvent != null) ReadyEvent();
        }

        // Construct new visual chart given a DataChart
        private void UpdateChart(DataChart dataChart) {
            lock (_objLock) {

                // Display default info if the parameter is null
                if (dataChart == null) {
                    DataChartOptions chartOps = new DataChartOptions("Unavailable");
                    chartOps.Description = "There is currently no data available to draw a chart.";
                    chart = new DataChart(chartOps, new DataSeries(""));
                    ChartInfo.Default();
                    OutputChart.Visibility = Visibility.Hidden;
                } else {

                    // Display chart if we have a valid datachart
                    ChartInfo.SetChart(dataChart.Options.Title, dataChart.Options.Description);
                    chart = dataChart;

                    bool isValid = false;
                    foreach (DataSeries s in dataChart.SeriesList.Values) {
                        if (s.Series.Count > 0) {
                            isValid = true;
                            break;
                        }
                    }
                    if (isValid) OutputChart.Visibility = Visibility.Visible;
                    else OutputChart.Visibility = Visibility.Hidden;
                }

                // Clear Chart
                OutputChart.Series.Clear();
                var palette = OutputChart.Palette;
                OutputChart.Palette = null;
                OutputChart.Palette = palette;

                // If a stacked chart type is selected, build a stackedSeries
                if (((TypeStringTuple)SeriesType.SelectedItem).Nonstacked == false) {

                    // Add each series to a stackedSeries class, then add that to the chart
                    var stackedSeries = Activator.CreateInstance(((TypeStringTuple)SeriesType.SelectedItem).ChartType) as DefinitionSeries;
                    foreach (DataSeries series in chart.SeriesList.Values) {
                        var definition = new SeriesDefinition();
                        definition.DependentValuePath = "DependentValue";
                        definition.IndependentValuePath = "IndependentValue";
                        definition.ItemsSource = series.Series;
                        stackedSeries.SeriesDefinitions.Add(definition);
                    }
                    OutputChart.Series.Add(stackedSeries);

                    // Else, if a nonstacked chart type is selected, build the appropriate series type
                } else {

                    switch (((TypeStringTuple)SeriesType.SelectedItem).Name) {
                        case "Line Chart":
                            LineSeries seriesA;
                            foreach (DataSeries s in chart.SeriesList.Values) {
                                seriesA = new LineSeries();
                                seriesA.DependentValuePath = "DependentValue";
                                seriesA.IndependentValuePath = "IndependentValue";
                                seriesA.ItemsSource = s.Series;
                                OutputChart.Series.Add(seriesA);
                            }
                            break;

                        case "Scatter Chart":
                            ScatterSeries seriesB;
                            foreach (DataSeries s in chart.SeriesList.Values) {
                                seriesB = new ScatterSeries();
                                seriesB.DependentValuePath = "DependentValue";
                                seriesB.IndependentValuePath = "IndependentValue";
                                seriesB.ItemsSource = s.Series;
                                OutputChart.Series.Add(seriesB);
                            }
                            break;

                        case "Column Chart":
                            ColumnSeries seriesC;
                            foreach (DataSeries s in chart.SeriesList.Values) {
                                seriesC = new ColumnSeries();
                                seriesC.DependentValuePath = "DependentValue";
                                seriesC.IndependentValuePath = "IndependentValue";
                                seriesC.ItemsSource = s.Series;
                                OutputChart.Series.Add(seriesC);
                            }
                            break;

                        case "Area Chart":
                            AreaSeries seriesD;
                            foreach (DataSeries s in chart.SeriesList.Values) {
                                seriesD = new AreaSeries();
                                seriesD.DependentValuePath = "DependentValue";
                                seriesD.IndependentValuePath = "IndependentValue";
                                seriesD.ItemsSource = s.Series;
                                OutputChart.Series.Add(seriesD);
                            }
                            break;

                        case "Pie Chart":
                            PieSeries seriesE;
                            foreach (DataSeries s in chart.SeriesList.Values) {
                                seriesE = new PieSeries();
                                seriesE.DependentValuePath = "DependentValue";
                                seriesE.IndependentValuePath = "IndependentValue";
                                seriesE.ItemsSource = s.Series;
                                OutputChart.Series.Add(seriesE);
                            }
                            break;

                        default:
                            BarSeries seriesF;
                            foreach (DataSeries s in chart.SeriesList.Values) {
                                seriesF = new BarSeries();
                                seriesF.DependentValuePath = "DependentValue";
                                seriesF.IndependentValuePath = "IndependentValue";
                                seriesF.ItemsSource = s.Series;
                                OutputChart.Series.Add(seriesF);
                            }
                            break;
                    }
                }

                // Setup chart legend
                int n = 0;
                OutputChart.LegendTitle = chart.Options.LegendTitle;
                foreach (LegendItem i in OutputChart.LegendItems) {
                    i.Content = chart.SeriesNameAt(n++);
                }
            }
        }

        #endregion


        #region **************** CHART SAVE/READ FUNCTIONALITY ****************

        // Save out Chart JPEG of currently selected algorithm
        public void SaveChartJPG(string filename) {
            if (filename != null && filename != String.Empty) {
                if (ChartAreaXAML.RenderSize.Height > 0 && ChartAreaXAML.RenderSize.Width > 0 && OutputChart.Visibility == Visibility.Visible) {
                    byte[] screenshot = ChartAreaXAML.GetJpgImage(1, 100);   // 1x scale, maximum quality
                    FileStream fileStream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
                    BinaryWriter binaryWriter = new BinaryWriter(fileStream);
                    binaryWriter.Write(screenshot);
                    binaryWriter.Close();
                } else {
                    MessageBox.Show("There is currently no chart information to save as a JPEG. Please select an appropriate algorithm on the Data Chart tab in order to export chart screenshots.", "Cannot Save Chart Screenshot", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        // Save out CSV of currently selected algorithm
        public void SaveChartCSV(string filename) {
            if (filename != null && filename != String.Empty) {
                if (OutputChart.Visibility == Visibility.Visible) {
                    chart.ConvertToCSV(filename);
                } else {
                    MessageBox.Show("There is currently no chart information to save as a CSV. Please select an appropriate algorithm on the Data Chart tab in order to export chart CSV.", "Cannot Save Chart CSV", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        // Clear Chart on reset
        private void ResetChartTab() {
            chart = null;
            OutputChart.Visibility = Visibility.Hidden;
        }

        #endregion


        #region **************** CHECK BOX AND CHART TYPE DROPDOWN BOX LOGIC ****************

        private void SeriesType_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (SelectedAlgorithm != null) LoadChart(chart);
        }

        private void CheckBox_Checked(object sender, RoutedEventArgs e) {
            // Deregister previous algorithm render event
            (SelectedAlgorithm.Algorithm as IChartRenderable).RenderChart -= UpdateChartData;

            // Find newly selected
            foreach (AlgorithmItem a in ChartInfo.Instance.AlgorithmsList) {
                if (a.Name != SelectedAlgorithm.Name && a.Selected == true) {
                    SelectedAlgorithm = a;
                    break;
                }
            }
            // Clear any other selected
            foreach (AlgorithmItem a in ChartInfo.Instance.AlgorithmsList) {
                if (a.Name != SelectedAlgorithm.Name && a.Selected == true) a.Selected = false;
            }

            // Update chart with newly selected algorithm and register render event
            // Have the dispatcher update the chart on the GUI
            (SelectedAlgorithm.Algorithm as IChartRenderable).RenderChart += UpdateChartData;
            LoadChart((AlgorithmSet.Instance[SelectedAlgorithm.Name] as IChartRenderable).GetRenderableChart());
        }

        private void CheckBox_Unchecked(object sender, RoutedEventArgs e) {
            SelectedAlgorithm.Selected = true;
        }

        // Lock and Unlock Algorithm Selection Checkboxes (for when processing)
        private void DisableCheckBoxes() {
            foreach (AlgorithmItem a in ChartInfo.Instance.AlgorithmsList) a.Enabled = false;
        }
        private void EnableCheckBoxes() {
            foreach (AlgorithmItem a in ChartInfo.Instance.AlgorithmsList) a.Enabled = true;
        }

        #endregion

    }

    // Chart ComboBox Item
    public class TypeStringTuple {
        public Type ChartType { get; set; }
        public string Name { get; set; }
        public bool Nonstacked { get; set; }
        public TypeStringTuple(Type type, string name, bool nonstacked) {
            ChartType = type;
            Name = name;
            Nonstacked = nonstacked;
        }
    }
}
